/**
 *    Copyright 2009-2020 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.executor;

import static org.apache.ibatis.executor.ExecutionPlaceholder.EXECUTION_PLACEHOLDER;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.cache.impl.PerpetualCache;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.statement.StatementUtil;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.logging.jdbc.ConnectionLogger;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.mapping.StatementType;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.LocalCacheScope;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.transaction.Transaction;
import org.apache.ibatis.type.TypeHandlerRegistry;

/**
 * @author Clinton Begin
 */
/*xxx: 主要提供了 缓存管理 和  事务管理 的基本功能*/
public abstract class BaseExecutor implements Executor {

  private static final Log log = LogFactory.getLog(BaseExecutor.class);

  /*xxx: Transaction对象，实现事务的提交，回滚 和 关闭操作*/
  protected Transaction transaction;
  /*xxx: 封装的 Executor 对象*/
  protected Executor wrapper;

  /*xxx: 一级缓存，是 会话级别 的缓存， 在MyBatis 中每创建一个 SqlSession对象，就表示开启一次数据库会话*/
  /*xxx: 在一次会话中，应用程序可能会在短时间内 ，如一个事务内，反复执行完全相同的 查询语句*/

  /*xxx: 延迟加载队列*/
  protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
  /*xxx: 一级缓存，用于缓存该 Executor 对象查询结果集  映射得到的结果对象*/
  protected PerpetualCache localCache;
  /*xxx: 一级缓存，用于缓存输出类型的参数*/
  protected PerpetualCache localOutputParameterCache;
  protected Configuration configuration;

  /*xxx: 用来记录 嵌套查询的层数*/
  protected int queryStack;
  private boolean closed;

  protected BaseExecutor(Configuration configuration, Transaction transaction) {
    this.transaction = transaction;
    this.deferredLoads = new ConcurrentLinkedQueue<>();
    this.localCache = new PerpetualCache("LocalCache");
    this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
    this.closed = false;
    this.configuration = configuration;
    this.wrapper = this;
  }

  @Override
  public Transaction getTransaction() {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    return transaction;
  }

  @Override
  public void close(boolean forceRollback) {
    try {
      try {
        /*xxx: 首先忽略 缓存的 sql 语句*/
        rollback(forceRollback);
      } finally {
        /*xxx: 然后关闭事务，根据参数决定是否关闭底层的数据库连接*/
        if (transaction != null) {
          transaction.close();
        }
      }
    } catch (SQLException e) {
      // Ignore. There's nothing that can be done at this point.
      log.warn("Unexpected exception on closing transaction.  Cause: " + e);
    } finally {
      transaction = null;
      deferredLoads = null;
      localCache = null;
      localOutputParameterCache = null;
      closed = true;
    }
  }

  @Override
  public boolean isClosed() {
    return closed;
  }

  @Override
  public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    /*xxx: 判断当前 Executor 是否已经关闭 */
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    /*xxx: 该方法  会 调用  localCache ,localOutputParameterCache两个缓存的 clear 方法，完成清理工作*/
    /*xxx: 这是 影响一级缓存中，数据存活时长的 第三个方面*/
    clearLocalCache();
    /*xxx: 调用 doUpdate 方法 执行sql语句*/
    return doUpdate(ms, parameter);
  }

  @Override
  public List<BatchResult> flushStatements() throws SQLException {
    return flushStatements(false);
  }

  public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException {
    /*xxx: 判断当前 Executor 是否已经关闭*/
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    /*xxx: isRollBack 表示是否 执行 Executor 中缓存的 sql语句，false 表示执行，true 表示不执行*/
    return doFlushStatements(isRollBack);
  }

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    /*xxx: 将参数 封装到 BoundSql 对象*/
    BoundSql boundSql = ms.getBoundSql(parameter);
    /*xxx: 创建 CacheKey 对象*/
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    /*xxx: 调用 重载，进行后续处理*/
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
  }

  @SuppressWarnings("unchecked")
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    /*xxx: 检测当前的 Executor 是否已经关闭 */
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      /*xxx: 非嵌套查询，且 <select> 节点配置的  flushCache 属性为 true ，则会清空一级缓存*/
      /*xxx: flushCache 配置项 是影响 一级缓存 结果对象存活时长的一个方面*/
      clearLocalCache();
    }
    List<E> list;
    try {
      /*xxx: 增加查询层数*/
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        /*xxx: 针对存储过程调用的处理, 在一级缓存命中时，获取缓存中保存的 输出类型参数， 并设置到用户传入的实参中*/
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        /*xxx: 其中会调用 doQuery完成数据库查询，并得到映射后的处理对象*/
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      /*xxx: 当前查询完成，查询层数减少*/
      queryStack--;
    }
    if (queryStack == 0) {
      /*xxx: 延迟加载的相关内容*/
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      /*xxx: 加载完成后，清空 deferredLoads 集合*/
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        /*xxx: 根据localCacheScope 配置 决定是否清空一级缓存*/
        /*xxx: localCacheScope配置 是 影响一级缓存中 结果对象 存活时长的 第二个方面*/
        clearLocalCache();
      }
    }
    return list;
  }

  @Override
  public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    return doQueryCursor(ms, parameter, rowBounds, boundSql);
  }

  @Override
  public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    DeferredLoad deferredLoad = new DeferredLoad(resultObject, property, key, localCache, configuration, targetType);
    if (deferredLoad.canLoad()) {
      deferredLoad.load();
    } else {
      deferredLoads.add(new DeferredLoad(resultObject, property, key, localCache, configuration, targetType));
    }
  }

  @Override
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    /*xxx: 检测当前 Executor是否已经关闭*/
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    /*xxx: 创建 CacheKey 对象 */
    CacheKey cacheKey = new CacheKey();
    /*xxx: 添加 MappedStatement的id,offset,limit,sql语句*/
    cacheKey.update(ms.getId());
    cacheKey.update(rowBounds.getOffset());
    cacheKey.update(rowBounds.getLimit());
    cacheKey.update(boundSql.getSql());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    // mimic DefaultParameterHandler logic
    /*xxx: 获取用户传入的 实参，并添加到 CacheKey对象宏*/
    for (ParameterMapping parameterMapping : parameterMappings) {
      if (parameterMapping.getMode() != ParameterMode.OUT) {
        /*xxx: 过滤掉 输出类型的 参数*/
        Object value;
        String propertyName = parameterMapping.getProperty();
        if (boundSql.hasAdditionalParameter(propertyName)) {
          value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {
          value = null;
        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          value = parameterObject;
        } else {
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        /*xxx: 将实参添加到 CacheKey对象中*/
        cacheKey.update(value);
      }
    }
    /*xxx: 如果Environment 的 id 不为空，则将其添加到 CacheKey中*/
    if (configuration.getEnvironment() != null) {
      // issue #176
      cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
  }

  @Override
  public boolean isCached(MappedStatement ms, CacheKey key) {
    return localCache.getObject(key) != null;
  }

  @Override
  public void commit(boolean required) throws SQLException {
    if (closed) {
      throw new ExecutorException("Cannot commit, transaction is already closed");
    }
    /*xxx: 首先清空一级缓存*/
    clearLocalCache();
    /*xxx: 执行缓存的sql 语句*/
    flushStatements();
    /*xxx: 根据 required参数，决定是否提交事务*/
    if (required) {
      transaction.commit();
    }
  }

  @Override
  public void rollback(boolean required) throws SQLException {
    if (!closed) {
      try {
        /*xxx: 首先清空一级缓存*/
        clearLocalCache();
        /*xxx: 忽略缓存的sql 语句*/
        flushStatements(true);
      } finally {
        if (required) {
          transaction.rollback();
        }
      }
    }
  }

  @Override
  public void clearLocalCache() {
    if (!closed) {
      localCache.clear();
      localOutputParameterCache.clear();
    }
  }

  protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;

  protected abstract List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException;

  protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
      throws SQLException;

  protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
      throws SQLException;

  protected void closeStatement(Statement statement) {
    if (statement != null) {
      try {
        statement.close();
      } catch (SQLException e) {
        // ignore
      }
    }
  }

  /**
   * Apply a transaction timeout.
   *
   * @param statement
   *          a current statement
   * @throws SQLException
   *           if a database access error occurs, this method is called on a closed <code>Statement</code>
   * @since 3.4.0
   * @see StatementUtil#applyTransactionTimeout(Statement, Integer, Integer)
   */
  protected void applyTransactionTimeout(Statement statement) throws SQLException {
    StatementUtil.applyTransactionTimeout(statement, statement.getQueryTimeout(), transaction.getTimeout());
  }

  private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter, BoundSql boundSql) {
    if (ms.getStatementType() == StatementType.CALLABLE) {
      final Object cachedParameter = localOutputParameterCache.getObject(key);
      if (cachedParameter != null && parameter != null) {
        final MetaObject metaCachedParameter = configuration.newMetaObject(cachedParameter);
        final MetaObject metaParameter = configuration.newMetaObject(parameter);
        for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
          if (parameterMapping.getMode() != ParameterMode.IN) {
            final String parameterName = parameterMapping.getProperty();
            final Object cachedValue = metaCachedParameter.getValue(parameterName);
            metaParameter.setValue(parameterName, cachedValue);
          }
        }
      }
    }
  }

  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    /*xxx: 在缓存中添加占位符*/
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      /*xxx: 调用 doQuery方法，完成 数据库查询操作，并返回结果对象*/
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      /*xxx: 删除占位符*/
      localCache.removeObject(key);
    }
    /*xxx: 将真正的结果对象添加到一级缓存中*/
    localCache.putObject(key, list);
    /*xxx: 是否为存储过程调用*/
    if (ms.getStatementType() == StatementType.CALLABLE) {
      /*xxx: 缓存输出类型的参数*/
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

  protected Connection getConnection(Log statementLog) throws SQLException {
    Connection connection = transaction.getConnection();
    if (statementLog.isDebugEnabled()) {
      return ConnectionLogger.newInstance(connection, statementLog, queryStack);
    } else {
      return connection;
    }
  }

  @Override
  public void setExecutorWrapper(Executor wrapper) {
    this.wrapper = wrapper;
  }

  /*xxx: 负责从 localCache 缓存中，延迟加载结果对象*/
  private static class DeferredLoad {

    /*xxx:外层对象对应的 MetaObject对象*/
    private final MetaObject resultObject;
    /*xxx: 延迟加载的属性名*/
    private final String property;
    /*xxx: 延迟加载的属性的类型*/
    private final Class<?> targetType;
    /*xxx: 延迟加载 的结果对象 在一级缓存中 相应的  CacheKey对象*/
    private final CacheKey key;
    /*xxx: 一级缓存*/
    private final PerpetualCache localCache;
    private final ObjectFactory objectFactory;
    /*xxx: 负责结果对象的类型转换*/
    private final ResultExtractor resultExtractor;

    // issue #781
    public DeferredLoad(MetaObject resultObject,
                        String property,
                        CacheKey key,
                        PerpetualCache localCache,
                        Configuration configuration,
                        Class<?> targetType) {
      this.resultObject = resultObject;
      this.property = property;
      this.key = key;
      this.localCache = localCache;
      this.objectFactory = configuration.getObjectFactory();
      this.resultExtractor = new ResultExtractor(configuration, objectFactory);
      this.targetType = targetType;
    }

    public boolean canLoad() {
      /*xxx: 检测 缓存 是否存在指定的结果对象*/
      return localCache.getObject(key) != null && localCache.getObject(key) != EXECUTION_PLACEHOLDER;
    }

    public void load() {
      @SuppressWarnings("unchecked")
      // we suppose we get back a List
              /*xxx: 从缓存中 查询指定的结果对象*/
      List<Object> list = (List<Object>) localCache.getObject(key);
      /*xxx: 将缓存的结果对象 转换成  指定类型*/
      Object value = resultExtractor.extractObjectFromList(list, targetType);
      /*xxx: 设置到外层对象的对应属性*/
      resultObject.setValue(property, value);
    }

  }

}
